/*=============================================================================
*
*   Copyright (C) 2020 All rights reserved.
*
*   Filename:		log.hpp
*
*   Author: Wang Zhecheng - wangzhecheng@yeah.net
*
*   Date: 2020-02-20 20:05
*
*   Last Editors: Wang Zhecheng - wangzhecheng@yeah.net
*
*   Last modified:	2020-03-03 15:54
*
*   Description:
*
=============================================================================*/
#ifndef LOG_H
#define LOG_H

#include "../util/debugger.hpp"
#include <boost/date_time/posix_time/posix_time_types.hpp>
#include <boost/log/attributes/mutable_constant.hpp>
#include <boost/log/attributes/timer.hpp>
#include <boost/log/expressions.hpp>
#include <boost/log/sources/logger.hpp>
#include <boost/log/sources/severity_logger.hpp>
#include <boost/log/support/date_time.hpp>
#include <boost/log/trivial.hpp>
#include <boost/log/utility/setup/common_attributes.hpp>
#include <boost/log/utility/setup/console.hpp>
#include <boost/log/utility/setup/file.hpp>

namespace logging  = boost::log;
namespace expr     = boost::log::expressions;
namespace sinks    = boost::log::sinks;
namespace attrs    = boost::log::attributes;
namespace src      = boost::log::sources;
namespace keywords = boost::log::keywords;

#define LOGGING_REGISTER()                                      \
  size_t              geoxfem::log::indent_level::m_indent = 0; \
  std::vector<size_t> geoxfem::log::indent_level::m_chapter =   \
    std::vector<size_t>();

#define GEOXFEM_LOG_SEV(lvl)                                                 \
  geoxfem::log::indent_level TEMP_VAR;                                       \
  BOOST_LOG_STREAM_WITH_PARAMS(                                              \
    geoxfem::log::Logger::getSevLogger(),                                    \
    (geoxfem::log::set_get_attrib(                                           \
      "File", std::string(__func__) + ": "                                   \
                + geoxfem::log::path_to_filename(__FILE__)))(                \
      geoxfem::log::set_get_attrib("Line", __LINE__))(                       \
      ::boost::log::keywords::severity = geoxfem::log::severity_level::lvl)) \
    << geoxfem::log::indent_level::getPrefix()

#define GEOXFEM_LOG()                                         \
  geoxfem::log::indent_level TEMP_VAR;                        \
  BOOST_LOG_STREAM_WITH_PARAMS(                               \
    geoxfem::log::Logger::getLogger(),                        \
    (geoxfem::log::set_get_attrib(                            \
      "File", std::string(__func__) + ": "                    \
                + geoxfem::log::path_to_filename(__FILE__)))( \
      geoxfem::log::set_get_attrib("Line", __LINE__)))        \
    << geoxfem::log::indent_level::getPrefix()

namespace geoxfem {
namespace log {

enum severity_level { detail, trace, process, info, warning, debug, error };
// The operator puts a human-friendly representation of the severity level to
// the stream
std::ostream& operator<<(std::ostream& strm, severity_level level) { /*{{{*/
  static const char* strings[] = {"dtl", "inf", "trc", "pro",
                                  "war", "dbg", "err"};
  if(static_cast<size_t>(level) < sizeof(strings) / sizeof(*strings))
    strm << strings[level];
  else
    strm << static_cast<int>(level);
  return strm;
} /*}}}*/

struct Logger { /*{{{*/
  static src::severity_logger<severity_level>& getSevLogger() {
    static src::severity_logger<geoxfem::log::severity_level> geoxfem_lg_sev;
    return geoxfem_lg_sev;
  }
  static src::logger& getLogger() {
    static src::logger geoxfem_lg;
    return geoxfem_lg;
  }
}; /*}}}*/

struct indent_level { /*{{{*/

  static size_t              m_indent;
  static std::vector<size_t> m_chapter;

  indent_level() {
    /// Write a message
    if(m_indent == m_chapter.size())
      m_chapter.push_back(1);
    else
      ++m_chapter.back();
    ++m_indent;
  }

  ~indent_level() {
    if(m_indent < m_chapter.size())
      m_chapter.pop_back();
    m_indent--;
  }

  static std::string getPrefix() {
    std::string s;
    for(size_t i = 0; i != m_indent; ++i)
      s.append("  ");
    s.append("Stage ");
    for(size_t i = 0; i != m_chapter.size(); ++i)
      s.append(std::to_string(m_chapter[i]) + '.');
    s.pop_back();
    s.append(":");
    return s;
  }
}; /*}}}*/

// Set attribute and return the new value
template <typename ValueType>
ValueType set_get_attrib(const char* name, ValueType value) { /*{{{*/
  auto attr = logging::attribute_cast<attrs::mutable_constant<ValueType>>(
    logging::core::get()->get_global_attributes()[name]);
  attr.set(value);
  return attr.get();
} /*}}}*/

void log_init(const severity_level& filter_level) { /*{{{*/
  logging::add_common_attributes();
  logging::core::get()->add_global_attribute(
    "File", attrs::mutable_constant<std::string>("\0"));
  logging::core::get()->add_global_attribute("Line",
                                             attrs::mutable_constant<int>(0));
  logging::core::get()->add_global_attribute("Uptime", attrs::timer());
  logging::formatter fmt =
    expr::stream
    << std::setw(3) << std::setfill(' ') << expr::attr<unsigned int>("LineID")
    << " ["
    << expr::format_date_time<attrs::timer::value_type>("Uptime", "%O:%M:%S")
    << "]"
    << expr::if_(expr::has_attr(
         "Severity"))[expr::stream
                      << "<" << expr::attr<severity_level>("Severity") << ">"]
    << expr::smessage << std::setfill(' ') << "  <--  ["
    << expr::attr<std::string>("File") << " @" << expr::attr<int>("Line")
    << "]";
  logging::formatter fmt_error =
    expr::stream
    << "\n\n***\n"
    << "***  "
       "---------------------------------------------------------------------"
       "----\n"
    << "***  Error:   Unable to " << expr::smessage << ".\n"
    << "***  Where:   This error was encountered inside "
    << expr::attr<std::string>("File") << " @" << expr::attr<int>("Line")
    << ".\n"
    << "***  "
       "---------------------------------------------------------------------"
       "----\n"
    << "***\n\n";

  auto console_sink = logging::add_console_log();
  console_sink->set_filter(
    expr::attr<severity_level>("Severity") >= filter_level
    && expr::attr<severity_level>("Severity") < severity_level::error);
  console_sink->set_formatter(fmt);

  auto console_error_sink = logging::add_console_log();
  console_error_sink->set_filter(expr::attr<severity_level>("Severity")
                                 == severity_level::error);
  console_error_sink->set_formatter(fmt_error);

  auto file_sink = logging::add_file_log(
    keywords::file_name     = "%m-%d-%H:%M_%N.log",  //文件名
    keywords::rotation_size = 10 * 1024 * 1024       //单个文件限制大小
  );
  file_sink->set_formatter(fmt);
  file_sink->locked_backend()->set_file_collector(sinks::file::make_collector(
    keywords::target   = "logs",           //文件夹名
    keywords::max_size = 50 * 1024 * 1024  //文件夹所占最大空间
    ));
  return;
} /*}}}*/

}  // namespace log
}  // namespace geoxfem
#endif /* LOG_H */
